﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using TracedReality.RayTracing;
using TracedReality.SpatialTrees;
using QiHe.Yaml.Grammar;

namespace TracedReality.Primitive
{
    /// <summary>
    /// A checkerboard of planes with alternating Materials.
    /// Since this class may cause a lot of geometry, it contains a kd-tree.
    /// </summary>
    public class Checkerboard : Shape
    {
        /// <summary>
        /// This is used internally to create all the FinitePlanes for the checkerboard.
        /// </summary>
        /// <param name="corner"></param>
        /// <param name="d1"></param>
        /// <param name="d2"></param>
        /// <param name="divs"></param>
        /// <param name="white"></param>
        /// <param name="black"></param>
        /// <returns></returns>
        private static FinitePlane[] makeCheckerboard(Vector corner, Vector d1, Vector d2, int divs, ref Material white, ref Material black)
        {
            Vector[,] tiles = Utilities.tile(corner, d1, d2, divs);
            FinitePlane[,] planes = new FinitePlane[divs, divs];
            for (int x = 0; x < divs; x++)
            {
                for (int y = 0; y < divs; y++)
                {
                    if (((x % 2) == 0) ^ ((y % 2) == 0))
                    {
                        planes[x, y] = new FinitePlane(tiles[x, y], d1 / divs, d2 / divs, ref white);
                    }
                    else
                    {
                        planes[x, y] = new FinitePlane(tiles[x, y], d1 / divs, d2 / divs, ref black);
                    }
                }
            }

            return Utilities.flatten(planes);
        }

        /// <summary>
        /// Internal kd-tree to speedup intersection.
        /// </summary>
        public ShapeKDTree tree;
        public Vector corner;
        public Vector dir1;
        public Vector dir2;
        public int divs;
        public Material material2;

        /// <summary>
        /// Create a Checkerboard over the given plane with the specified number of divisions per side
        /// and the given Materials.
        /// </summary>
        /// <param name="c">Corner of the plane.</param>
        /// <param name="d1">First direction.</param>
        /// <param name="d2">Second direction.</param>
        /// <param name="d">Number of divisions.</param>
        /// <param name="white">First Material.</param>
        /// <param name="black">Second Material.</param>
        public Checkerboard(Vector c, Vector d1, Vector d2, int d, ref Material white, ref Material black)
        {
            corner = c;
            dir1 = d1;
            dir2 = d2;
            divs = d;
            material = white;
            material2 = black;

            Vector z = c + d1;
            Vector y = c + d2;
            Vector x = c + d1 + d2;
            Vector min = Vector.Min(z, Vector.Min(y, Vector.Min(x, c))) - Settings.EPSILON;
            Vector max = Vector.Max(z, Vector.Max(y, Vector.Max(x, c))) + Settings.EPSILON;
            bound = new BoundingBox(min, max);

            material = null;

            tree = new ShapeKDTree(bound);

            FinitePlane[] planes = makeCheckerboard(c, d1, d2, d, ref white, ref black);
            foreach (FinitePlane fp in planes) { tree.add(fp); }
            tree.split(Settings.maxSDSNodeCount, Settings.maxSDSDepth);
        }

        public override bool Equals(Shape s)
        {
            if (s is Checkerboard) { return Equals((Checkerboard)s); }
            return false;
        }

        public bool Equals(Checkerboard c)
        {
            throw new NotImplementedException();
        }

        public override bool Similar(Shape s)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Test if the given Ray intersects this Checkerboard.
        /// </summary>
        /// <param name="ray">Ray to test for intersection.</param>
        /// <param name="hit">Hit to update with intersection info.</param>
        /// <returns>1 if the Ray intersects this Checkerboard from the front,
        /// -1 if the Ray intersects this Checkerboard from the back,
        /// 0 if no intersection occurs.</returns>
        public override int Intersect(Ray ray, Hit hit)
        {
            return tree.castRay(ray, hit);
        }

        public static Checkerboard Parse(MappingEntry entry)
        {
            if (entry == null) { throw new ArgumentNullException("entry"); }

            Mapping map = (Mapping)entry.Value;
            Material material1 = Material.Parse(map.Find("material1"));
            Material material2 = Material.Parse(map.Find("material2"));
            MappingEntry found = map.Find("corner");
            if (found == null) { throw new ArgumentException("No corner entry"); }
            Vector corner = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("dir1");
            if (found == null) { throw new ArgumentException("No dir1 entry"); }
            Vector dir1 = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("dir2");
            if (found == null) { throw new ArgumentException("No dir2 entry"); }
            Vector dir2 = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("divisions");
            if (found == null) { throw new ArgumentException("No divisions entry"); }
            int divs = int.Parse(found.Value.ToString());

            return new Checkerboard(corner, dir1, dir2, divs, ref material1, ref material2);
        }

        public override string ToString()
        {
            return "Checkerboard: { corner: " + corner + ", dir1: " + dir1 + ", dir2: " + dir2 + ", divisions: " + divs + ", material1: " + material + ", material2: " + material2 + " }";
        }
    }
}
